1   package org.apache.solr.handler;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.lang.invoke.MethodHandles;
21  import java.nio.file.Path;
22  import java.nio.file.Paths;
23  import java.util.concurrent.Callable;
24  import java.util.concurrent.Future;
25  
26  import org.apache.lucene.codecs.CodecUtil;
27  import org.apache.lucene.index.SegmentInfos;
28  import org.apache.lucene.store.Directory;
29  import org.apache.lucene.store.FSDirectory;
30  import org.apache.lucene.store.IOContext;
31  import org.apache.lucene.store.IndexInput;
32  import org.apache.lucene.util.Version;
33  import org.apache.solr.common.SolrException;
34  import org.apache.solr.core.DirectoryFactory;
35  import org.apache.solr.core.SolrCore;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  public class RestoreCore implements Callable<Boolean> {
40  
41    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
42  
43    private final String backupName;
44    private final String backupLocation;
45    private final SolrCore core;
46  
47    public RestoreCore(SolrCore core, String location, String name) {
48      this.core = core;
49      this.backupLocation = location;
50      this.backupName = name;
51    }
52  
53    @Override
54    public Boolean call() throws Exception {
55      return doRestore();
56    }
57  
58    private boolean doRestore() throws Exception {
59  
60      Path backupPath = Paths.get(backupLocation).resolve(backupName);
61      String restoreIndexName = "restore." + backupName;
62      String restoreIndexPath = core.getDataDir() + restoreIndexName;
63  
64      Directory restoreIndexDir = null;
65      Directory indexDir = null;
66      try (Directory backupDir = FSDirectory.open(backupPath)) {
67  
68        final Version version = IndexFetcher.checkOldestVersion(SegmentInfos.readLatestCommit(backupDir));
69  
70        restoreIndexDir = core.getDirectoryFactory().get(restoreIndexPath,
71            DirectoryFactory.DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
72  
73        //Prefer local copy.
74        indexDir = core.getDirectoryFactory().get(core.getIndexDir(),
75            DirectoryFactory.DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
76  
77        //Move all files from backupDir to restoreIndexDir
78        for (String filename : backupDir.listAll()) {
79          checkInterrupted();
80          log.info("Copying file {} to restore directory ", filename);
81          try (IndexInput indexInput = backupDir.openInput(filename, IOContext.READONCE)) {
82            Long checksum = null;
83            try {
84              checksum = CodecUtil.retrieveChecksum(indexInput);
85            } catch (Exception e) {
86              log.warn("Could not read checksum from index file: " + filename, e);
87            }
88            long length = indexInput.length();
89            IndexFetcher.CompareResult compareResult = IndexFetcher.compareFile(indexDir, version, filename, length, checksum);
90            if (!compareResult.equal || (!compareResult.checkSummed && (filename.endsWith(".si")
91                || filename.endsWith(".liv") || filename.startsWith("segments_")))) {
92              restoreIndexDir.copyFrom(backupDir, filename, filename, IOContext.READONCE);
93            } else {
94              //prefer local copy
95              restoreIndexDir.copyFrom(indexDir, filename, filename, IOContext.READONCE);
96            }
97          } catch (Exception e) {
98            throw new SolrException(SolrException.ErrorCode.UNKNOWN, "Exception while restoring the backup index", e);
99          }
100       }
101       log.debug("Switching directories");
102       IndexFetcher.modifyIndexProps(core, restoreIndexName);
103 
104       boolean success;
105       try {
106         core.getUpdateHandler().newIndexWriter(false);
107         openNewSearcher();
108         success = true;
109         log.info("Successfully restored to the backup index");
110       } catch (Exception e) {
111         //Rollback to the old index directory. Delete the restore index directory and mark the restore as failed.
112         log.warn("Could not switch to restored index. Rolling back to the current index");
113         Directory dir = null;
114         try {
115           dir = core.getDirectoryFactory().get(core.getDataDir(), DirectoryFactory.DirContext.META_DATA,
116               core.getSolrConfig().indexConfig.lockType);
117           dir.deleteFile(IndexFetcher.INDEX_PROPERTIES);
118         } finally {
119           if (dir != null) {
120             core.getDirectoryFactory().release(dir);
121           }
122         }
123 
124         core.getDirectoryFactory().doneWithDirectory(restoreIndexDir);
125         core.getDirectoryFactory().remove(restoreIndexDir);
126         core.getUpdateHandler().newIndexWriter(false);
127         openNewSearcher();
128         throw new SolrException(SolrException.ErrorCode.UNKNOWN, "Exception while restoring the backup index", e);
129       }
130       if (success) {
131         core.getDirectoryFactory().doneWithDirectory(indexDir);
132         core.getDirectoryFactory().remove(indexDir);
133       }
134 
135       return true;
136     } finally {
137       if (restoreIndexDir != null) {
138         core.getDirectoryFactory().release(restoreIndexDir);
139       }
140       if (indexDir != null) {
141         core.getDirectoryFactory().release(indexDir);
142       }
143     }
144   }
145 
146   private void checkInterrupted() throws InterruptedException {
147     if (Thread.currentThread().isInterrupted()) {
148       throw new InterruptedException("Stopping restore process. Thread was interrupted.");
149     }
150   }
151 
152   private void openNewSearcher() throws Exception {
153     Future[] waitSearcher = new Future[1];
154     core.getSearcher(true, false, waitSearcher, true);
155     if (waitSearcher[0] != null) {
156       waitSearcher[0].get();
157     }
158   }
159 }